#include <cmath>

#include "FrakOutData.h"
#include "FrakOutDataTableModel.h"

const QString FrakOutDataTableModel::ColumnCellSizeString           = "Cell Size";
const QString FrakOutDataTableModel::ColumnNumCellsString           = "# Cells";
const QString FrakOutDataTableModel::ColumnFractalDimensionString   = "Fractal Dimension";
const int FrakOutDataTableModel::ColumnCellSize                     = 0;
const int FrakOutDataTableModel::ColumnNumCells                     = 1;
const int FrakOutDataTableModel::ColumnFractalDimension             = 2;
const int FrakOutDataTableModel::ColumnCount                        = 3;

FrakOutDataTableModel::FrakOutDataTableModel(FrakOutData& fracalcData, QObject *parent) :
        QAbstractTableModel(parent), m_fracalcData(fracalcData)
{
}

void FrakOutDataTableModel::addCellForRow(int row, const QPair<int,int>& cell)
{
    m_fracalcData.addCell(row, cell);
    emit dataChanged(index(row, ColumnCellSize), index(row, ColumnFractalDimension));
}

void FrakOutDataTableModel::addToBeginning(const FrakOutDataItem& item)
{
    QModelIndex index;

    m_fracalcData.addItem(0, item);
    beginInsertRows(index, 0, 0);
    insertRow(0, index);
    endInsertRows();
}

void FrakOutDataTableModel::addToEnd(const FrakOutDataItem& item)
{
    QModelIndex index;

    int rowAfterInsertion = m_fracalcData.numItems();

    m_fracalcData.addItem(rowAfterInsertion, item);
    beginInsertRows(index, rowAfterInsertion, rowAfterInsertion);
    insertRow(rowAfterInsertion, index);
    endInsertRows();
}

void FrakOutDataTableModel::clear()
{
    beginRemoveRows(QModelIndex(), 0, m_fracalcData.numItems() - 1);
    m_fracalcData.clearSheets();
    endRemoveRows();
}

int FrakOutDataTableModel::columnCount(const QModelIndex& /*parent*/) const
{
    return ColumnCount;
}

QVariant FrakOutDataTableModel::data(const QModelIndex& index, int role) const
{
    QVariant value;

    if (role == Qt::DisplayRole)
    {
        int column = index.column();
        int row = index.row();
        const FrakOutDataItem& item = m_fracalcData.items().at(index.row());

        if (column == ColumnCellSize)
        {
            value.setValue(item.cellSize());
        }
        else if (column == ColumnNumCells)
        {
            value.setValue(item.numCells());
        }
        else
        {
            if (row == 0)
            {
                value.setValue(QString("-"));
            }
            else
            {
                const FrakOutDataItem &prevItem = m_fracalcData.items().at(row - 1);
                if (item.numCells() == 0 || prevItem.numCells() == 0)
                {
                    value.setValue(QString("-"));
                }
                else
                {
                    double scaleFactor = 1.0 / pow(m_fracalcData.cellScaleFactor(), -row);
                    double fractalDimension = log(double(item.numCells())) / log(scaleFactor);
                    value.setValue(fractalDimension);
                }
            }
        }
    }

    return value;
}

QVariant FrakOutDataTableModel::headerData(int section, Qt::Orientation orientation, int role) const
{
    QVariant header;

    if (role == Qt::DisplayRole)
    {
        // TODO: Need to translate the header strings.
        if (orientation == Qt::Horizontal)
        {
            if (section == ColumnCellSize)
            {
                header.setValue(ColumnCellSizeString);
            }
            else if (section == ColumnNumCells)
            {
                header.setValue(ColumnNumCellsString);
            }
            else
            {
                header.setValue(ColumnFractalDimensionString);
            }
        }
    }

    return header;
}

void FrakOutDataTableModel::removeCellForRow(int row, const QPair<int,int>& cell)
{
    m_fracalcData.removeCell(row, cell);
    emit dataChanged(index(row, ColumnCellSize), index(row, ColumnFractalDimension));
}

void FrakOutDataTableModel::removeCellsForRow(int row)
{
    m_fracalcData.removeCells(row);
    emit dataChanged(index(row, ColumnCellSize), index(row, ColumnFractalDimension));
}

void FrakOutDataTableModel::removeFromBeginning()
{
    removeRow(0, index(0, 0));
}

void FrakOutDataTableModel::removeFromEnd()
{
    int row = m_fracalcData.numItems() - 1;

    removeRow(row, index(row, 0));
}

bool FrakOutDataTableModel::removeRows(int row, int count, const QModelIndex&)
{
    int endRow = row + count - 1;

    beginRemoveRows(QModelIndex(), row, endRow);
    for (int i = row; i <= endRow; i++)
    {
        m_fracalcData.removeItem(i);
    }
    endRemoveRows();

    return true;
}

int FrakOutDataTableModel::rowCount(const QModelIndex& /*parent*/) const
{
    return m_fracalcData.numItems();
}

void FrakOutDataTableModel::setCellsForRow(int row, const QSet<QPair<int,int> >& cells)
{
    m_fracalcData.setCells(row, cells);
}

void FrakOutDataTableModel::updateModel()
{
    beginResetModel();
    endResetModel();
}
